package websocket

import (
	"context"
	"net/http"
	"sync"
	"time"

	"dev.ixmxm.top/rms/codes"
	"dev.ixmxm.top/rms/utils/log"
	"github.com/gorilla/websocket"
)

type state uint8

const (
	INIT         state = 0
	CONNECTED    state = 1
	DISCONNECTED state = 2
)

type ReadHandler = func(appData string)
type PingHandler = func(appData string) error
type PongHandler = func(appData string) error
type CloseHandler = func(code int, text string) error

type Session interface {
	SetPingHandler(pingHandler PingHandler)
	SetPongHandler(pongHandler PongHandler)
	SetCloseHandler(closeHandler CloseHandler)
	SendJson(data interface{})
	SendControl(messageType int, data []byte, deadline time.Time) error
	RemoteAddr() string
	IsOK() bool
	Close()
}

func NewDefaultServerSession(ctx context.Context, writer http.ResponseWriter, req *http.Request) (*DefaultSession, codes.Code) {
	//创建连接
	upgrade := &websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}
	conn, err := upgrade.Upgrade(writer, req, nil)

	if err != nil {
		return nil, codes.ErrWebsocketsUpgrade
	}

	ctxNew, cancel := context.WithCancel(ctx)
	s := &DefaultSession{
		conn:         conn,
		sendCh:       make(chan interface{}, 5),
		cancel:       cancel,
		lastCommTime: time.Now(),
		state:        CONNECTED,
	}
	s.start(ctxNew)
	return s, nil
}

type DefaultSession struct {
	conn          *websocket.Conn
	sendCh        chan interface{}
	cancel        context.CancelFunc
	lastCommTime  time.Time
	state         state
	readHd        ReadHandler
	pingHd        PingHandler
	pongHd        PongHandler
	closeHd       CloseHandler
	sendCloseOnce sync.Once
}

func (s *DefaultSession) GetConn() *websocket.Conn {
	return s.conn
}

func (s *DefaultSession) IsOK() bool {
	return s.state == CONNECTED
}

func (s *DefaultSession) RemoteAddr() string {
	return s.conn.RemoteAddr().String()
}

func (s *DefaultSession) SendJson(data interface{}) {
	defer func() {
		if recover() != nil {
			log.Warn("the send channel is closed")
		}
	}()
	if s.IsOK() {
		s.sendCh <- data
	}
}

func (s *DefaultSession) SendControl(messageType int, data []byte, deadline time.Time) error {
	return s.conn.WriteControl(messageType, data, deadline)
}

//关闭连接
func (s *DefaultSession) Close() {
	defer func() {
		s.cancel()
		s.state = DISCONNECTED
	}()
	s.sendClose()
	if s.conn != nil {
		if err := s.conn.Close(); err != nil {
			// log.Error("close websocket connection failed:", err.Error())
		}
	}
	defer func() {
		if recover() != nil {
			// log.Warn("the send channel is closed")
		}
	}()
	close(s.sendCh)
}

func (s *DefaultSession) UpdateLastCommTime() {
	s.lastCommTime = time.Now()
}

func (s *DefaultSession) SetReadHandler(readHandler ReadHandler) {
	//fmt.Printf("WebSocket接收到的数据1:%s", readHandler)
	s.readHd = readHandler
}

func (s *DefaultSession) SetPingHandler(pingHandler PingHandler) {
	//fmt.Printf("WebSocket接收到的ping数据1:%s", pingHandler)
	s.pingHd = pingHandler
}

func (s *DefaultSession) SetPongHandler(pongHandler PongHandler) {
	s.conn.SetPongHandler(pongHandler)
}

func (s *DefaultSession) SetCloseHandler(closeHandler CloseHandler) {
	s.conn.SetCloseHandler(closeHandler)
}

func (s *DefaultSession) start(ctx context.Context) {
	s.conn.SetPingHandler(s.defaultPingHandler)
	if s.pongHd != nil {
		s.conn.SetPongHandler(s.pongHd)
	} else {
		s.conn.SetPongHandler(s.defaultPongHandler)
	}
	if s.closeHd != nil {
		s.conn.SetCloseHandler(s.closeHd)
	} else {
		s.conn.SetCloseHandler(s.defaultCloseHandler)
	}
	s.startSessionCheck(ctx)
	s.startWrite(ctx)
	s.startRead(ctx)
}

func (s *DefaultSession) startRead(ctx context.Context) {
	go func() {
		for {
			select {
			case <-ctx.Done():
				return
			default:
				_, message, err := s.conn.ReadMessage()
				if err != nil {
					if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived, websocket.CloseAbnormalClosure) {
						// log.Warnf("ws:%s error close:%s", s.conn.RemoteAddr(), err.Error())
						// s.Close()
						return
					}
					// log.Errorf("ws read error:%s", err.Error())
					return
				}
				//fmt.Printf("WebSocket接收到的数据4:%s", string(message))
				if string(message) == "ping" {
					s.lastCommTime = time.Now()
				}
				if s.readHd != nil {
					//s.conn.SetPingHandler(s.defaultReadHandler)
					s.readHd(string(message))
				} else {
					log.Infof("websockets receive:%s from [%s] ,but read handler is nil", string(message), s.conn.RemoteAddr().String())
				}
			}
		}
	}()
}

func (s *DefaultSession) startWrite(ctx context.Context) {
	go func() {
		for {
			select {
			case v := <-s.sendCh:
				if !s.IsOK() {
					continue
				}
				if err := s.conn.WriteJSON(v); err != nil {
					//log.Error("Write websocket message failed:", err.Error())
					// s.Close()
				}
			case <-ctx.Done():
				return
			}
		}
	}()
}

// websocket心跳检测
func (s *DefaultSession) startSessionCheck(ctx context.Context) {
	go func() {
		tick := time.NewTicker(50 * time.Second)
		defer tick.Stop()
		for {
			select {
			case <-tick.C:
				//心跳检测
				if time.Now().Sub(s.lastCommTime) > 10*time.Second {
					//log.Warn("session heartbeat timeout:", s.RemoteAddr())
					s.Close()
					return
				}
			case <-ctx.Done():
				return
			}
		}
	}()
}

func (s *DefaultSession) defaultPingHandler(appData string) error {
	s.lastCommTime = time.Now()
	//log.Info("ping handler:", appData)
	if err := s.conn.WriteControl(websocket.PongMessage, []byte("pong"), time.Now().Add(5*time.Second)); err != nil {
		// log.Error("Write pong message failed:", err.Error())
	}
	if s.pingHd != nil {
		return s.pingHd(appData)
	}
	return nil
}

func (s *DefaultSession) defaultPongHandler(appData string) error {
	log.Info("pong handler:", appData)
	log.Infof("websockets session [%s] receive pong:%s", s.RemoteAddr(), appData)
	return nil
}

func (s *DefaultSession) defaultCloseHandler(code int, text string) error {
	// log.Warn("close receive:", code, text)
	s.sendClose()
	return nil
}

func (s *DefaultSession) sendClose() {
	s.sendCloseOnce.Do(func() {
		if err := s.SendControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "close"), time.Now().Add(5*time.Second)); err != nil {
			// log.Error("send close message error:", err.Error())
		}
	})
}
